/* Copyright (c) Microsoft Corporation. All rights reserved. */

using System;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using Microsoft.LearningComponents;
using System.IO;
using System.Collections.Specialized;
using System.Globalization;
using Microsoft.LearningComponents.DataModel;
using System.Web;

namespace Microsoft.LearningComponents
{

    /// <summary>
    /// Class that represents an handler for LRM content.
    /// </summary>
    internal class LrmRloHandler : RloHandler
    {
        AssessmentItemManager m_assessmentItemMgr;
        string m_headGradingViewScript;

        internal LrmRloHandler()
        {
            AIResources.Culture = Thread.CurrentThread.CurrentCulture;;

            m_assessmentItemMgr = new AssessmentItemManager();
            string numberDecimalSeparator = CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator;
            // convert the number decimal separator into a hex number. Note if the number decimal separator
            // is longer than one character, the current script will break. At the time of writing this,
            // multi-character decimal separator support does not seem like a worthwhile use of dev time.
            string hex = Convert.ToInt32(numberDecimalSeparator[0]).ToString("X", CultureInfo.InvariantCulture);

            // replace tags in the grading view script that won't change for the life of this object. The only
            // remaining tag to replace is <%=FormId%>, which is replaced during grading view rendering.
            string maximumComment = BaseSchemaInternal.CommentFromLearnerItem.MaxCommentLength.ToString(CultureInfo.InvariantCulture);
            m_headGradingViewScript = AIResources.HeadGradingViewScript.Replace(
                "<%=DecimalSeparator%>", numberDecimalSeparator).Replace(
                "<%=DecimalSeparatorHex%>", String.Format(CultureInfo.InvariantCulture, "{0}", hex)).Replace(
                "<%=ScoreOutOfRange%>", AIResources.ScoreOutOfRange).Replace(
                "<%=MaxCommentLength%>", maximumComment).Replace(
                "<%=CommentTooLong%>", String.Format(CultureInfo.InvariantCulture,
                    AIResources.InstructorCommentTooLong, maximumComment));
        }

        private AssessmentItemManager AssessmentItemManager
        {
            get { return m_assessmentItemMgr; }
        }

        /// <summary>
        /// Process data returned from a form generated by an instance of this class. Also sets the data model's
        /// evaluation points field to the total points, computed from the autograding or teacher grading.
        /// </summary>
        /// <param name="context">
        /// The context within which to process the form data. </param>
        /// <param name="formData">The collection of information from the form that should be processed.</param>
        /// <param name="files">Collection of valid files, posted from the request. This may be a subset of the files 
        /// included in the request, as some posted files may have been removed by the application as invalid.</param>
        /// <remarks>
        /// <p>Data that does not correspond to expected information is ignored.</p>
        /// </remarks>
        /// <exception cref="InvalidFormDataException">Thrown if posted data contains invalid data.</exception>
        public override void ProcessFormData(RloProcessFormDataContext context, NameValueCollection formData, IDictionary<string, HttpPostedFile> files)
        {
            // Do two passes here through all the interactions. This allows detecting any error in the posted data 
            // before making changes to the data model based on posted data

            AssessmentItemManager.ProcessFormContext = context;

            LearningDataModel learningDataModel = context.LearningDataModel;

            // Validate the form data for all interactions. Any validation error throws InvalidFormDataException.
            foreach (Interaction interaction in learningDataModel.Interactions)
            {
                FormDataProcessor processor = m_assessmentItemMgr.GetFormDataProcessor(interaction);
                // must check that processor is non null, since GetFormDataProcessor() can return null
                if (processor != null)
                {
                    processor.ValidateFormData(formData, files); // throws exception if not valid
                }
            }
            // Process the form data.  This won't execute if ValidateFormData threw and exception, above.
            // Keep a running sum the Interaction.Evaluation.Points values to set the page's Points value.
            float? totalPoints = null;
            foreach (Interaction interaction in learningDataModel.Interactions)
            {
                FormDataProcessor processor = m_assessmentItemMgr.GetFormDataProcessor(interaction);
                // must check that processor is non null, since GetFormDataProcessor() can return null.
                // If it is null, any item score associated with this interaction is not totalled into
                // EvaluationPoints.
                if (processor != null)
                {
                    processor.ProcessFormData(formData, files);
                    if (interaction.Evaluation.Points.HasValue)
                    {
                        if (totalPoints.HasValue)
                        {
                            totalPoints += interaction.Evaluation.Points.Value;
                        }
                        else
                        {
                            totalPoints = interaction.Evaluation.Points.Value;
                        }
                    }
                }
            }
            learningDataModel.EvaluationPoints = totalPoints;

            if (context.View == SessionView.Execute)
            {
                SetPageHasBeenAutograded(learningDataModel);
            }
        }

        /// <summary>
        /// Returns true if the reader is positioned on an assessment item IMG tag and sets srcIndex to the index of the
        /// src attribute.
        /// </summary>
        private static bool IsAITag(HtmlTextReader reader, out int srcIndex)
        {
            if (reader.NodeType == HtmlNodeType.Element
                && String.Compare(reader.Name, "img", StringComparison.OrdinalIgnoreCase) == 0)
            {
                if (reader.HasAttributes)
                {
                    for (int i = 0; i < reader.AttributeCount; i++)
                    {
                        if (String.Compare("src", reader.GetAttributeName(i), StringComparison.OrdinalIgnoreCase) == 0
                            && reader.GetAttributeValue(i).ToString().Contains("mslamrk"))
                        {
                            srcIndex = i;
                            return true;
                        }
                    }
                }
            }
            srcIndex = -1;
            return false;
        }

        /// <summary>
        /// Encodes text to be included in a [,] separated list in a correct response pattern.
        /// </summary>
        internal static string EncodePattern(string text)
        {
            return text.Replace("[,", "[,&").Replace("[.", "[.&");
        }

        /// <summary>
        /// Decodes text that was included in a [,] separated list in a correct response pattern.
        /// </summary>
        internal static string DecodePattern(string text)
        {
            return text.Replace("[,&", "[,").Replace("[.&", "[.");
        }

        private static string[] comma = { "[,]" };
        /// <summary>
        /// Splits a pattern containing "[,]" substrings into a string array.
        /// </summary>
        /// <param name="pattern"></param>
        /// <returns>Substrings split on the "[,]" substring.</returns>
        internal static string[] SplitPattern(string pattern)
        {
            return pattern.Split(comma, StringSplitOptions.None);
        }



        /// <summary>
        /// Render the requested view into the output stream.
        /// </summary>
        /// <param name="context">The context within which to render the page.</param>
        /// <remarks>
        /// When this method returns the <paramref name="context"/> OutputStream will contain 
        /// the rendered file.
        /// <p>
        /// The following methods and properties must be return valid values from 
        /// the <paramref name="context"/>:
        /// <ul>
        /// <li>EmbeddedUiResourcePath, must be non-null</li>
        /// <li>FormElementId</li>
        /// <li>GetInputStream</li>
        /// <li>OutputStream</li>
        /// <li>View</li>
        /// </ul>
        /// </p>
        /// <p>
        /// Additionally, if the following properties are set, they will be used:
        /// <ul>
        /// <li>FormElementAction</li>
        /// <li>HiddenControls</li>
        /// <li>ScriptToRender</li>
        /// </ul>
        /// </p>
        /// All other properties on <paramref name="context"/> are ignored.
        /// </remarks>
        /// <exception cref="FileNotFoundException">The requested file attachment can't be found.</exception>
        public override void Render(RloRenderContext context)
        {
            AIResources.Culture = Thread.CurrentThread.CurrentCulture;;

            // string is the key (which is AssessmentItem.Id_AssessmentItem.Type)
            // int is the ordinal (0 based) which is the number of times the key has been processed
            Dictionary<string, int> assessmentItems = new Dictionary<string, int>();

            // The most common case is that the file is in the package
            Stream inputStream = null;
            AssessmentItemManager.DataModelContext = context;
            LearningDataModel learningDataModel = context.LearningDataModel;

            try
            {
                int srcIndex; // represents the index of the "src" attribute on an <img> node.
                // If this is the first time the page is being rendered, parse the page and determine 
                // the interactions on the page.
                if (context.View == SessionView.Execute)
                {
                    if (!GetPageHasBeenVisited(learningDataModel))
                    {
                        using (inputStream = context.GetInputStream())
                        {
                            // If the file being requested is the default file for the current activity, 
                            if (context.IsResourceEntryPoint)
                            {
                                // find all the assessment items (<IMG> tags that contain the text "mslamrk" as part of the src attribute.)
                                using (HtmlTextReader reader = new HtmlTextReader(inputStream))
                                {
                                    while (reader.Read())
                                    {
                                        if (IsAITag(reader, out srcIndex))
                                        {
                                            try
                                            {
                                                AssessmentItem ai = AssessmentItem.Parse(reader.GetAttributeValue(srcIndex));
                                                AssessmentItemRenderer renderer = AssessmentItemManager.GetRenderer(ai);
                                                renderer.TryAddToDataModel();
                                            }
                                            catch (FormatException)
                                            {
                                                // skip this one.  This is mirrored below in the 2nd pass.
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        SetPageHasBeenVisited(learningDataModel);
                    }
                }

                // must get the input stream again since it may not be possible to seek back to the beginning
                using (inputStream = context.GetInputStream())
                {
                    if (context.Response != null)
                    {
                        // Clear the output response
                        context.Response.Clear();
                    }

                    // If the file being requested is the default file for the current activity, 
                    if (context.IsResourceEntryPoint)
                    {
                        if (context.View == SessionView.Execute)
                        {
                            // Set ExitMode to suspend so that when a student exits the activity it is left in a suspended state.
                            // This way if the activity is reactivated, the student's previous answers are intact.
                            learningDataModel.NavigationRequest.ExitMode = ExitMode.Suspended;
                        }

                        DetachableStream detachable = new DetachableStream(context.OutputStream);
                        // Parse through the input stream again, this time rendering into the output as we go.
                        using (StreamWriter writer = new StreamWriter(detachable))
                        {
                            using (HtmlTextReader reader = new HtmlTextReader(inputStream))
                            {
                                while (reader.Read())
                                {
                                    if (IsAITag(reader, out srcIndex))
                                    {
                                        try
                                        {
                                            AssessmentItem ai = AssessmentItem.Parse(reader.GetAttributeValue(srcIndex));
                                            AssessmentItemRenderer renderer = AssessmentItemManager.GetRenderer(ai);
                                            if (assessmentItems.ContainsKey(ai.RenderKey))
                                            {
                                                assessmentItems[ai.RenderKey] += 1;
                                            }
                                            else
                                            {
                                                assessmentItems.Add(ai.RenderKey, 0);
                                            }
                                            writer.Write(renderer.Render(assessmentItems[ai.RenderKey]).ToString());
                                        }
                                        catch (FormatException)
                                        {
                                            // skip this one.  This is mirrored above in the 1st pass.
                                        }
                                    }
                                    else
                                    {
                                        HandleNode(reader, writer);
                                    }
                                }
                            }
                            // don't allow closing the StreamWriter to close the context.OutputStream.
                            writer.Flush();
                            detachable.Detach();
                        }

                        // set the response type
                        context.SetOutputStreamExtension(Path.GetExtension(context.RelativePath));
                    }
                    else
                    {
                        // for a non-entry-point file, copy the file directly to the output stream
                        context.WriteFileToResponse(context.RelativePath);
                    }
                }

                

                return;
            }
            catch (FileNotFoundException)
            {
                // This means the requested file is not in the package. That's not necessarily a problem, since it
                // may be a request for an attachment.
            }

            // We got here because the file is not in the package. In that case, render it if it is a file attachment
            int beginAttachmentInfo = context.RelativePath.IndexOf("/~RLO/", StringComparison.Ordinal);
            if (beginAttachmentInfo != -1)
            {
                // attachmentInfo should be of the form <interactionId>/<attachmentIndex>, so split it into the parts
                string attachmentInfo = context.RelativePath.Substring(beginAttachmentInfo + 6);

                RenderFileAttachment(context, attachmentInfo);
            }
            else
            {
                // This means the requested file is not in the package, nor is it a request for an attachment.
                throw new FileNotFoundException(AIResources.FileNotFound);
            }
        }

        /// <summary>
        /// Render a file attachment.  File attachments will have a relativePath of the form:
        /// .../~RLO/&lt;interactionId&gt;/&lt;attachmentIndex&gt;
        /// </summary>
        /// <param name="context"></param>
        /// <param name="attachmentInfo">The portion of the path to the file after the "/~RLO" portion.</param>
        /// <exception cref="FileNotFoundException">The requested file attachment can't be found.</exception>
        private static void RenderFileAttachment(RloRenderContext context, string attachmentInfo)
        {
            AIResources.Culture = Thread.CurrentThread.CurrentCulture;;

            // File attachments will have a relativePath of the form:
            //  .../~RLO/<interactionId>/<attachmentIndex>
            // 
            // If it's a request to view a file attachment, then there will be extension data in the data model of the form:
            //      ms.learningcomponents.fileAttachment.<interactionId>.<attachmentIndex>

            // find the /~RLO/ portion of the path, so that we can find the beginning of the <interactionId>/<attachmentIndex>
            // part of the path. 

            // attachmentInfo should be of the form <interactionId>/<attachmentIndex>, so split it into the parts
            string[] splitAttachmentInfo = attachmentInfo.Split(new char[] { '/' });
            LearningDataModel learningDataModel = context.LearningDataModel;

            //if(learningDataModel.Interactions.Contains(splitAttachmentInfo[0]))
            if(learningDataModel.InteractionListContains(splitAttachmentInfo[0]))
            {
                //Interaction i = learningDataModel.Interactions[splitAttachmentInfo[0]];
                Interaction i = learningDataModel.InteractionListElement(splitAttachmentInfo[0]);
                string fileAttachmentKey = FileAttachmentKey(splitAttachmentInfo[1]);
                if (i.ExtensionData.ContainsKey(fileAttachmentKey))
                {
                    byte[] fileAttachment = (byte[])i.ExtensionData[fileAttachmentKey];
                    if (context.Response != null)
                    {
                        context.Response.Clear();
                    }
                    context.SetOutputStreamExtension((string)i.ExtensionData[FileAttachmentExtensionKey(splitAttachmentInfo[1])]);
                    context.WriteFileToResponse(fileAttachment);
                    return;
                }
            }

            // couldn't get the file
            throw new FileNotFoundException(AIResources.FileAttachmentNotFound);
        }

        /// <summary>
        /// Given the file attachment key, return the file attachment extension key to retrieve the file
        /// attachment extension from the interaction's extension data.
        /// </summary>
        internal static string FileAttachmentExtensionKey(string attachmentIndex)
        {
            return InteractionExtensionDataKeys.FileExtension(attachmentIndex);
        }

        /// <summary>
        /// Returns the file attachment key to use in an interaction's extension data to retrieve the
        /// byte array file attachment.
        /// </summary>
        internal static string FileAttachmentKey(string attachmentIndex)
        {
            return InteractionExtensionDataKeys.FileAttachment(attachmentIndex);
        }

        /// <summary>
        /// Output the current node, or the changes required to the current node by the rendering code, to the writer.
        /// &lt;img&gt; nodes representing assessment items are handled elsewhere.
        /// </summary>
        private void HandleNode(HtmlTextReader reader, StreamWriter writer)
        {
            AIResources.Culture = Thread.CurrentThread.CurrentCulture;;

            RloRenderContext context = AssessmentItemManager.RenderContext;

            if (reader.NodeType == HtmlNodeType.Element)
            {
                if (IsNamed(reader, "body"))
                {
                    const string ecs = " ECS_ViewType=\"{0}\" leftmargin=0 topmargin=0 rightmargin=0 bottommargin=0 ";
                    const string form = "<form NAME=\"{0}\" {1}METHOD=\"post\" ENCTYPE=\"multipart/form-data\" style=\"height:100%; width:100%; border:none; margin:0\">";
                    const string hidDetach = "<INPUT Name=\"hidDetach\" TYPE=\"HIDDEN\" value=\"0\">";
                    StringWriter bodyWriter = new StringWriter(CultureInfo.InvariantCulture);
                    reader.CopyNode(bodyWriter);
                    string body = bodyWriter.ToString();
                    bodyWriter.Close();
                    switch (context.View)
                    {
                        case SessionView.Execute:
                            // update the <body> to include ECS_ViewType attribute and margin attributes
                            body = body.Insert(5, String.Format(CultureInfo.InvariantCulture, ecs, "2"));
                            break;
                        case SessionView.RandomAccess:
                            // update the <body> to include ECS_ViewType attribute and margin attributes
                            body = body.Insert(5, String.Format(CultureInfo.InvariantCulture, ecs, "6"));
                            break;
                        case SessionView.Review:
                            // update the <body> to include ECS_ViewType attribute and margin attributes
                            body = body.Insert(5, String.Format(CultureInfo.InvariantCulture, ecs, "4"));
                            break;
                    }
                    writer.Write(body);

                    writer.Write(String.Format(CultureInfo.InvariantCulture,
                        form, "frmPage", " "));
                    writer.Write(hidDetach);

                    WriteFormHiddenControls(writer, context.FormHiddenControls);
                    return;
                }
                // if ShowReviewerInformation is false, check all table elements for a bgColor="#ff99cb"
                // and remove them from display.
                else if (!context.ShowReviewerInformation && IsNamed(reader, "table") && IsBgColorFF99CB(reader))
                {
                    reader.Skip();
                    return;
                }
            }
            else if (reader.NodeType == HtmlNodeType.EndElement)
            {
                // insert a script section before the </head>
                if (IsNamed(reader, "head"))
                {
                    switch (context.View)
                    {
                        case SessionView.Execute:
                            writer.Write(AIResources.HeadExecuteViewScript);
                            break;
                        case SessionView.RandomAccess:
                            // Security issue, FormElementId must be javascript safe.
                            writer.Write(m_headGradingViewScript.Replace("<%=FormId%>", "frmPage"));
                            break;
                        case SessionView.Review:
                            writer.Write(AIResources.ReviewViewScript);
                            break;
                    }
                    reader.CopyNode(writer);
                    return;
                }
                else if (IsNamed(reader, "body"))
                {
                    // insert any RloRenderContext.ScriptToRender and the </form> before the </body>
                    if (!String.IsNullOrEmpty(context.ScriptToRender))
                    {
                        writer.WriteLine("<script>");
                        writer.WriteLine(context.ScriptToRender);
                        writer.WriteLine("</script>");
                    }
                    writer.Write("</form></body>");
                    return;
                }
            }
            reader.CopyNode(writer);
        }

        /// <summary>
        /// Returns true if the reader is positioned on a node that has a bgColor="#ff99cb" attribute.
        /// </summary>
        private static bool IsBgColorFF99CB(HtmlTextReader reader)
        {
            if (reader.HasAttributes)
            {
                for (int i = 0; i < reader.AttributeCount; i++)
                {
                    if (String.Compare("bgColor", reader.GetAttributeName(i), StringComparison.OrdinalIgnoreCase) == 0
                        && String.Compare("#ff99cb", reader.GetAttributeValue(i).ToString(), StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Write out the hidden controls from the context.  hiddenControls is a dictionary where the key is the id of the
        /// control and the value is the value of the control, both are plain text and not Html.
        /// </summary>
        private static void WriteFormHiddenControls(StreamWriter writer, ReadOnlyDictionary<string, string> hiddenControls)
        {
            foreach (string key in hiddenControls.Keys)
            {
                writer.Write(String.Format(CultureInfo.InvariantCulture,
                    "<INPUT NAME=\"{0}\" TYPE=\"HIDDEN\" value=\"{1}\">",
                    new PlainTextString(key).ToHtmlString(),
                    new PlainTextString(hiddenControls[key]).ToHtmlString()));
            }
        }

        /// <summary>
        /// Returns "true" if the reader's node is named the supplied name, case-insensitive.
        /// </summary>
        private static bool IsNamed(HtmlTextReader reader, string name)
        {
            return 0 == String.Compare(reader.Name, name, StringComparison.OrdinalIgnoreCase);
        }

        /// <summary>
        /// Determine from data model if the page has been visited by learner yet
        /// </summary>
        private static bool GetPageHasBeenVisited(LearningDataModel learningDataModel)
        {
            return learningDataModel.ExtensionData.ContainsKey(InteractionExtensionDataKeys.PageRendered);
        }

        /// <summary>
        /// Remember that the page has been visited.
        /// </summary>
        private static void SetPageHasBeenVisited(LearningDataModel learningDataModel)
        {
            // setting the value to "true" is arbitrary, as the existence of the key in the extension data is
            // sufficient.
            learningDataModel.ExtensionData[InteractionExtensionDataKeys.PageRendered] = true;
        }

        /// <summary>
        /// Determine from data model if the page has been autograded.
        /// </summary>
        private static bool GetPageHasBeenAutograded(LearningDataModel learningDataModel)
        {
            return learningDataModel.ExtensionData.ContainsKey(InteractionExtensionDataKeys.PageAutograded);
        }

        /// <summary>
        /// Remember that the page has been autograded.
        /// </summary>
        private static void SetPageHasBeenAutograded(LearningDataModel learningDataModel)
        {
            // setting the value to "true" is arbitrary, as the existence of the key in the extension data is
            // sufficient.
            learningDataModel.ExtensionData[InteractionExtensionDataKeys.PageAutograded] = true;
        }

        /// <summary>
        /// Requests the RloHandler to do whatever is required to exit from the current activity.
        /// This request may only be issued when the session is in Execute view and is not active -- it is 
        /// either Completed or Abandoned.
        /// </summary>
        /// <param name="context">The context within which the command is processed</param>
        /// <remarks>
        /// This method should only be called for the <c>SessionView.Execute</c> view.  However,
        /// no checks are done internally to verify this - if this is called with other views,
        /// unexpected results will occur.
        /// </remarks>
        public override void ProcessSessionEnd(RloDataModelContext context)
        {
            LearningDataModel learningDataModel = context.LearningDataModel;
            // Set ExitMode to suspend so that when a student exits the activity it is left in a suspended state.
            // This way if the activity is reactivated, the student's previous answers are intact.
            learningDataModel.NavigationRequest.ExitMode = ExitMode.Suspended;
            // If the page has never been visited, "visit" it.
            if (!GetPageHasBeenVisited(learningDataModel))
            {
                AssessmentItemManager.DataModelContext = context;
                // Get the input stream containing the primary file from the resource associated with the 
                // current activity in the session. 
                using (Stream inputStream = context.GetInputStream())
                {
                    // find all the assessment items (<IMG> tags that contain the text "mslamrk" as part of the src attribute.)
                    using (HtmlTextReader reader = new HtmlTextReader(inputStream))
                    {
                        int srcIndex;
                        while (reader.Read())
                        {
                            if (IsAITag(reader, out srcIndex))
                            {
                                try
                                {
                                    AssessmentItem ai = AssessmentItem.Parse(reader.GetAttributeValue(srcIndex));
                                    AssessmentItemRenderer renderer = AssessmentItemManager.GetRenderer(ai);
                                    renderer.TryAddToDataModel();
                                }
                                catch (FormatException)
                                {
                                    // skip this one.
                                }
                            }
                        }
                    }
                }
                SetPageHasBeenVisited(learningDataModel);
            }
            // If the page has never been autograded, call ProcessSessionEnd on the form data processors
            if (!GetPageHasBeenAutograded(learningDataModel))
            {
                AssessmentItemManager.ProcessFormContext = new RloProcessFormDataContext(SessionView.Execute, learningDataModel);
                float? totalPoints = null;
                foreach (Interaction interaction in learningDataModel.Interactions)
                {
                    FormDataProcessor processor = m_assessmentItemMgr.GetFormDataProcessor(interaction);
                    // must check that processor is non null, since GetFormDataProcessor() can return null.
                    // If it is null, any item score associated with this interaction is not totalled into
                    // EvaluationPoints.
                    if (processor != null)
                    {
                        processor.ProcessSessionEnd(context);
                        if (interaction.Evaluation.Points.HasValue)
                        {
                            if (totalPoints.HasValue)
                            {
                                totalPoints += interaction.Evaluation.Points;
                            }
                            else
                            {
                                totalPoints = interaction.Evaluation.Points;
                            }
                        }
                    }
                }
                learningDataModel.EvaluationPoints = totalPoints;
                SetPageHasBeenAutograded(learningDataModel);
            }
        }

        /// <summary>
        /// Requests the RloHandler to process a reactivation request and clear the appropriate data 
        /// values from the data model. The request may only be issued when the session is in RandomAccess 
        /// view and is not active -- it is either Completed or Abandoned.
        /// </summary>
        /// <param name="context"></param>
        public override void Reactivate(RloReactivateContext context)
        {
            // Scenarios: instructor reactivates an assignment so a student can try again.  In all cases, if the
            // student resubmits an assignment autograding occurs and all scores are set to the autograded scores.
            // 1. Instructor wishes to make it so that if the student doesn't try again, and the instructor
            //    collects the assignment back, that the original autograded scores are applied to the assignment.
            // 2. Instructor wishes to make it so that if the student doesn't try again, and the instructor
            //    collects the assignment back, that the instructor's overrides of the autograded scores remain.
            // Likewise, the instructor may wish to keep or discard instructor comments.

            foreach (Interaction interaction in context.LearningDataModel.Interactions)
            {
                // If user requested resetting evaluation points, then change it back to 
                // the autograded scores and clear rubrics.
                if (context.ResetEvaluationPoints)
                {
                    interaction.Evaluation.Points = interaction.Score.Raw;
                    // If there are rubrics associated with the question, clear the IsSatisfied
                    // state
                    foreach (Rubric rubric in interaction.Rubrics)
                    {
                        rubric.IsSatisfied = null;
                    }
                }

                // If user requested to reset evaluation comments, then clear all comments.
                if (context.ResetEvaluationComments)
                {
                    interaction.Evaluation.Comments.Clear();
                }
            }
        }
    }
}
